package qc.module.iot.protocol.sl427;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qc.common.core.enums.iot.IotPacketsDataFrameTypeEnum;
import qc.common.core.enums.iot.IotPacketsDataProtocolEnum;
import qc.common.core.utils.CrcUtil;
import qc.common.core.utils.LocalDateTimeUtil;
import qc.common.core.utils.RadixConvertUtil;
import qc.module.iot.protocol.IotFrameBodyDataItem;
import qc.module.iot.protocol.IotFrameBodyDataItemTypeEnum;
import qc.module.iot.protocol.IotFrameParseBase;
import qc.module.iot.protocol.IotFrameParseResult;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * SL427(原先是SZY206、SZY427)数据帧解析类，提供SL427协议的报文分解、数据帧解析、回复报文生成方法
 *
 * @author QuCheng Tech
 * @since 2025/11/12
 */
public class SL427_FrameParse extends IotFrameParseBase {
    private static final Logger logger = LoggerFactory.getLogger(SL427_FrameParse.class);

    /**
     * 数据帧报文协议
     */
    private static final IotPacketsDataProtocolEnum protocol = IotPacketsDataProtocolEnum.SL_427;

    /**
     * 解析数据帧错误提示信息前缀
     */
    private static final String parseErrorMessagePrefix = "使用SL427协议解析数据帧时错误，";

    /**
     * 数据帧最小长度，包含起始符和结束符的最小有效长度
     */
    private static final int Frame_Length_Min_Total = 13;

    //每种数据协议中使用静态定义协议中的数据帧起始符、结束符等，通过构造函数传入到父类中
    /**
     * 数据帧起始符字节
     */
    private static final byte Frame_Start_Byte = 0x68;
    /**
     * 数据帧结束符字节
     */
    private static final byte Frame_End_Byte = 0x16;

    /**
     * 监测参数无数据或未采集该参数值时的所有字节取值
     */
    private static final byte NO_VALUE_Byte = (byte) 0xAA;

    public SL427_FrameParse() {
        //通过调用父类的构造函数传入协议中的固定内容
        super(protocol, new byte[]{Frame_Start_Byte}, new byte[]{Frame_End_Byte}, Frame_Length_Min_Total);
        //也可以在调用构造函数后显式对变量或调用方法进行赋值
        //this
    }

    /**
     * 解析数据帧
     *
     * @param inputBytes    数据帧报文字节数组，数组起始必须时数据值对应协议的起始符
     * @param parseBodyData 是否解析报文中的正文内容数据，为false时表示只解析数据帧基本结构框架，为true时表示解析解析结构框架和正文内容数据
     * @return qc.module.iot.protocol.IotFrameParseResult
     * @author QuCheng Tech
     * @since 2025/11/5
     */
    @Override
    public IotFrameParseResult parse(byte[] inputBytes, boolean parseBodyData) {
        logger.debug("parseBaseStruct");

        //初始化解析返回结果，设置解析成功标记为false
        IotFrameParseResult result = new IotFrameParseResult();
        result.setParseSuccess(false);
        result.setProtocol(protocol);
        result.setDataTime(null);//初始化数据时标为null
        //输入的字节数组赋值，根据数据协议确定是否要对Hex字符串和Ascii字符串进行赋值
        result.setInputBytes(inputBytes);
        result.setInputHexString(RadixConvertUtil.toHexString(inputBytes));

        /* SL427 上行报文数据帧
         * 结构框架：帧起始符 长度 帧起始符 控制域 地址域 功能码 数据域 附加信息域 校验码 帧结束符
         * 长度字节：   2     1     1    1/2    5     1     n     2/7     1      1
         * 固定内容： 帧起始符-68、帧结束符-16
         * 解析举例：68 26 68 B3 0012340098 C0 07000000000010000000310100000000800000000000010036001205042500 DF 16
         * 解析举例：68 1C 68 b3 3200100110 c0 AAAAAAAAAA05010000000000200000001030032200 12 16
         */
        if (inputBytes == null || inputBytes.length < this.frameMinLength)
            return returnParseError(result, "数据帧报文内容为空");
        //记录当前解析判断到的位置索引，数据帧的每个部分在解析前均要判断输入的字节数组总长是否足够
        int index = 0x0;
        //判断有效数据帧报文最小总长度
        int frameMinTotalLength = 0x0;
        //数据帧的每个结构框架部分的长度，判断到每个部分长度时进行赋值
        int framePartLength = 0x0;

        //帧起始符，长度为1字节
        //framePartLength=this.frameStartBytes.length;//可以从静态定义中读取长度，也可以直接固定长度
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的帧起始符时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        for (int i = 0; i < framePartLength; i++) {
            if (inputBytes[index + i] != this.frameStartBytes[i])
                return returnParseError(result, "帧起始符错误");
        }
        index += framePartLength;//帧起始符判断通过，索引加上帧起始符长度

        //长度，长度为1字节
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的长度时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte contentLengthByte = inputBytes[index];
        index += framePartLength;//帧中心站地址判断通过，索引加上帧中心站地址长度

        //长度后的帧起始符2，长度为1字节
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的帧起始符2时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        //byte frameStartByte2 = inputBytes[index];
        for (int i = 0; i < framePartLength; i++) {
            if (inputBytes[index + i] != this.frameStartBytes[i])
                return returnParseError(result, "帧起始符2错误");
        }
        index += framePartLength;//帧起始符判断通过，索引加上帧起始符长度

        //根据数据帧中的长度获取报文正文内容，长度表示控制域、地址域、功能码、数据与、附加信息域的字节总和
        //长度为正文/用户数据区的长度，不包含帧头（帧起始符1、长度、帧起始符2）和校验码及帧结束符的字节数
        int contentLength = RadixConvertUtil.computeIntValue(contentLengthByte);
        result.setBodyLength(contentLength);
        //根据内容长度判断报文长度是否足够
        if (contentLength > 0x0) {
            logger.debug("报文中内容正文长度解析为：" + contentLength);
            //SL427协议中的长度是指控制域、地址域、功能码、数据域、附加信息域的字节总数，不包含前面已经解析的帧起始符、长度，也不包含未解析的帧起始符、校验码、帧结束符
            //判断报文最小总长=已解析的3字节(帧起始符1、长度、帧起始符2) + 长度字节值 + 未解析的2字节(校验码、帧结束符)
            frameMinTotalLength = index + contentLength + 0x2;
            if (inputBytes.length < frameMinTotalLength)
                return returnParseError(result, "根据报文中的正文长度[" + contentLength + "]判断报文长度不足，报文总长度应至少为" + frameMinTotalLength);

            //根据报文正文长度获取正文字节数组
            byte[] bodyDataBytes = Arrays.copyOfRange(inputBytes, index, index + contentLength);
            logger.debug("报文中内容正文HEX为：" + RadixConvertUtil.toHexString(bodyDataBytes));
            index += contentLength;//索引加上报文正文长度

            /* SL427 数据帧正文/用户数据区格式
             * 结构框架：控制域 地址域 功能码 数据域 附加信息域
             * 长度字节： 1/2    5     1     n     2/7
             * 解析举例：B3 0012340098 C0 07000000000010000000310100000000800000000000010036001205042500
             * 解析举例：b3 3200100110 c0 AAAAAAAAAA05010000000000200000001030032200
             */

            //控制域，长度为1字节，分帧传输时为2字节
            int controlBytesLength = 0x1;
            byte controlByte = bodyDataBytes[0x0];
            int controlIntValue = RadixConvertUtil.computeIntValue(controlByte);
            logger.debug("报文中的控制域byte为：" + controlByte + " int值为：" + controlIntValue + " hex为：" + RadixConvertUtil.toHexString(controlByte));
            //控制域，表示报文传输方向和传输信息类型
            //控制域D7位表示传输方向，DIR=0表示下行，DIR=1表示上行
            if ((controlByte & 0x80) == 0x80)//0x80=1000 0000
                result.setUpDirection(true);
            else
                result.setUpDirection(false);
            //控制域D6位表示分帧，DIV=0表示不拆分数据帧，DIR=1表示拆分数据帧
            boolean divideFrame = false;
            if ((controlByte & 0x40) == 0x40) {//0x40=0100 0000
                //如果是分帧，控制域长度为2字节
                divideFrame = true;
                controlBytesLength = 0x2;
            }
            //控制域D5-D4位表示帧计数位
            //控制域D3-D0位表示命令与类型码，为0表示确认帧、为其他＞0表示自报帧

            //地址域，长度为5字节，起始位置索引从控制域后开始
            byte[] stationAddressBytes = Arrays.copyOfRange(bodyDataBytes, controlBytesLength, controlBytesLength + 0x5);
            result.setStationAddressBytes(stationAddressBytes);
            result.setStationAddress(RadixConvertUtil.toHexString(stationAddressBytes));

            //功能码，长度为1字节，用户自定义AFN由2字节组成（第1字节固定为0xFF，第2字节用户自定义）
            byte functionCodeByte = bodyDataBytes[controlBytesLength + 0x5];//起始位置索引=控制域字节长度+地址域长度5字节
            int functionCodeIntValue = RadixConvertUtil.computeIntValue(functionCodeByte);
            logger.debug("报文中的功能码byte为：" + functionCodeByte + " int值为：" + functionCodeIntValue + " hex为：" + RadixConvertUtil.toHexString(functionCodeByte));
            result.setFunctionCodeBytes(new byte[]{functionCodeByte});
            result.setFunctionCode(RadixConvertUtil.toHexString(functionCodeByte));
            //功能码解析，根据功能码对数据值类型进行赋值，确定是心跳还是数据帧
            SL427_FrameFunctionCodeEnum functionCodeEnum = SL427_FrameFunctionCodeEnum.getSL427_FrameFunctionCodeEnum(functionCodeIntValue);
            logger.debug("报文中的功能码byte为：" + functionCodeByte + " 功能码枚举为：" + functionCodeEnum);
            result.setFrameName(functionCodeEnum.getName());
            IotPacketsDataFrameTypeEnum frameType = IotPacketsDataFrameTypeEnum.UN_DEFINE;
            if (functionCodeEnum == SL427_FrameFunctionCodeEnum.HEART_BEAT)//链路检测，心跳数据
                frameType = IotPacketsDataFrameTypeEnum.HEART_BEAT;
            else if (functionCodeEnum == SL427_FrameFunctionCodeEnum.REPORT_DATA_C0H)//自报实时数据
                frameType = IotPacketsDataFrameTypeEnum.DATA;
            else if (functionCodeEnum.getIndex() >= SL427_FrameFunctionCodeEnum.REPORT_DATA_81H.getIndex() &&
                    functionCodeEnum.getIndex() <= SL427_FrameFunctionCodeEnum.REPORT_DATA_84H.getIndex())
                frameType = IotPacketsDataFrameTypeEnum.DATA;
            result.setFrameType(frameType);

            //数据域，起始位置索引从功能码后开始=控制域字节长度+6(地址域长度5字节、功能码长度1字节)
            byte[] userDataBytes = Arrays.copyOfRange(bodyDataBytes, controlBytesLength + 0x6, contentLength);
            String userDataHexString = RadixConvertUtil.toHexString(userDataBytes);
            logger.debug("报文中内容正文中的数据域HEX为：" + RadixConvertUtil.toHexString(bodyDataBytes));
            result.setBodyDataBytes(userDataBytes);
            result.setBodyDataString(userDataHexString);

            //判断如果要解析正文中的数据进行解析
            if (parseBodyData == true && result.getBodyDataBytes() != null && result.getBodyDataBytes().length > 0x0) {
                //解析报文正文判断帧类型需要使用控制域和功能码，与解析正文方法中的顺序保持一致
                List<IotFrameBodyDataItem> bodyDataItems = parseBodyData(new byte[]{functionCodeByte, controlByte},
                        result.getBodyDataBytes());
                if (bodyDataItems != null && bodyDataItems.size() > 0x0)
                    result.setBodyDataItems(bodyDataItems);
            }

            //附加信息域，密码、时间标签
            //密码，长度为2字节
            //byte[] passwordBytes = Arrays.copyOfRange(inputBytes, index, index + framePartLength);
            //result.setPasswordBytes(passwordBytes);
            //result.setPassword(RadixConvertUtil.toHexString(passwordBytes));
        }

        //CRC校验码，长度为1字节
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的CRC校验码时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte crcByte = inputBytes[index];
        result.setCrcBytes(new byte[]{crcByte});
        //判断CRC是否正确
        int crcIntValue = RadixConvertUtil.computeIntValue(crcByte);
        //SL427协议的校验码是用户数据区的CRC校验（不包含帧头的起始符和长度的3字节）
        byte[] calcCrcBytes = Arrays.copyOfRange(inputBytes, 0x3, index);//计算CRC输入的字节数组不包含crc的2个字节
        int calcCrcIntValue = CrcUtil.crc7(calcCrcBytes);
        logger.debug("计算CRC，报文：" + RadixConvertUtil.toHexString(calcCrcBytes));
        logger.debug("报文中CRC值为：" + crcIntValue + "，计算出的CRC值为：" + calcCrcIntValue + "，计算出的CRC字符串为：" + String.format("%02X", calcCrcIntValue));
        if (crcIntValue != calcCrcIntValue) {
            //计算出的crc与报文中的crc不一致
        }

        index += framePartLength;//报文CRC校验码判断通过，索引加上CRC校验码的长度

        //帧结束符，长度为1字节
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的帧结束符时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        //判断帧结束符是否正确
        byte framesEndByte = inputBytes[index];
        if (framesEndByte != Frame_End_Byte)
            return returnParseError(result, "数据帧中的帧结束符错误，帧结束符应该为[" + RadixConvertUtil.toHexString(Frame_End_Byte) +
                    "],实际报文中的结束符为[" + RadixConvertUtil.toHexString(framesEndByte) + "]");
        index += framePartLength;//报文结束符判断通过，索引加上报文结束符的长度

        //全部判断和验证均通过，解析成功；设置完整的数据帧字节数组
        result.setParseSuccess(true);
        byte[] frameBytes = Arrays.copyOfRange(inputBytes, 0x0, index);
        result.setParseFrameBytes(frameBytes);
        result.setParseFrameString(RadixConvertUtil.toHexString(frameBytes));

        logger.info("报文解析成功，正文长度[{}]，中心站地址[{}]，遥测站地址[{}]，密码[{}]，功能码[{}]，帧名称[{}]，帧类型[{}]，正文内容[{}]",
                result.getBodyLength(), result.getCenterAddress(), result.getStationAddress(), result.getPassword(),
                result.getFunctionCode(), result.getFrameName(), result.getFrameType(), result.getBodyDataString());

        return result;
    }

    /**
     * 返回数据帧解析错误结果
     *
     * @param result   返回解析结果对象
     * @param errorMsg 错误信息，返回结果中自动加上统一的前缀
     * @return qc.module.iot.protocol.IotFrameParseResult
     * @author QuCheng Tech
     * @since 2025/11/7
     */
    IotFrameParseResult returnParseError(IotFrameParseResult result, String errorMsg) {
        if (result == null) {
            result = new IotFrameParseResult();
            result.setParseSuccess(false);
        }

        result.setParseErrorMsg(parseErrorMessagePrefix + errorMsg);

        return result;
    }

    /**
     * 解析数据帧正文内容
     *
     * @param functionBytes  功能码字节数组，长度可以为1或＞1，长度＜1时无效不进行正文内容解析
     * @param functionBytes, bodyBytes 正文内容字节数组
     * @return java.util.List<qc.module.iot.protocol.IotFrameBodyDataItem>
     * @author QuCheng Tech
     * @since 2025/11/13
     */
    @Override
    public List<IotFrameBodyDataItem> parseBodyData(byte[] functionBytes, byte[] bodyBytes) {
        logger.debug("parseBodyData");
        //如果功能码或正文字节数组为空不能解析正文内容
        if (functionBytes != null && functionBytes.length > 0x0 &&
                bodyBytes != null && bodyBytes.length > 0x0) {
            //需要先根据功能码AFN确定帧类型，再根据控制域的低4位确定自报数据的种类
            //传入参数中第1个字节为功能码、第2/3个字节为控制域
            //控制域，长度为1字节，分帧传输时为2字节
            byte functionCodeByte = functionBytes[0x0];
            int functionCodeIntValue = RadixConvertUtil.computeIntValue(functionCodeByte);
            SL427_FrameFunctionCodeEnum functionCodeEnum = SL427_FrameFunctionCodeEnum.getSL427_FrameFunctionCodeEnum(functionCodeIntValue);
            logger.debug("报文中的功能码AFN byte为：" + functionCodeByte + " int值为：" + functionCodeIntValue + " hex为：" + RadixConvertUtil.toHexString(functionCodeByte));
            byte controlByte = functionBytes[0x1];
            //控制域低4位为0011表示流量（水量），数据域中5字节表示流量、5字节表示水量
            int controlIntValue = RadixConvertUtil.computeIntValue(controlByte);
            //控制域，表示报文传输方向和传输信息类型
            //控制域D7位表示传输方向，DIR=0表示下行，DIR=1表示上行
            boolean isUpDirection = true;
            if ((controlByte & 0x80) == 0x80)//0x80=1000 0000
                isUpDirection = true;
            else
                isUpDirection = false;
            //控制域D6位表示分帧，DIV=0表示不拆分数据帧，DIR=1表示拆分数据帧
            boolean divideFrame = false;
            if ((controlByte & 0x40) == 0x40) {//0x40=0100 0000
                //如果是分帧，控制域长度为2字节
                divideFrame = true;
            }
            //控制域D5-D4位表示帧计数位
            //控制域D3-D0位表示命令与类型码，为0表示确认帧、为其他＞0表示自报帧，根据低4位解析出帧命令类型 
            SL427_FrameControlCommandTypeEnum commandTypeEnum = SL427_FrameControlCommandTypeEnum.getSL427_FrameControlTypeEnum(controlIntValue & 0x0F);
            logger.debug("报文中的控制域byte为：" + controlByte + " int值为：" + controlIntValue + " hex为：" + RadixConvertUtil.toHexString(controlByte) + " 帧命令类型为：" + commandTypeEnum);

            List<IotFrameBodyDataItem> result = new LinkedList<>();
            //根据不同的功能码调用不同的报文正文解析方法
            if (functionCodeEnum == SL427_FrameFunctionCodeEnum.HEART_BEAT) {
                //心跳数据，AFN=02H
                IotFrameBodyDataItem bodyDataItem = new IotFrameBodyDataItem();
                bodyDataItem.setBytes(bodyBytes);
                bodyDataItem.setHexString(RadixConvertUtil.toHexString(bodyBytes));
                bodyDataItem.setCode(functionCodeEnum.getName());
                bodyDataItem.setName(functionCodeEnum.getName());
                bodyDataItem.setType(IotFrameBodyDataItemTypeEnum.STATUS);
                //AFN=02H时，数据为1字节，F0H表示登录、F1H表示退出登录、F2H表示在线保持
                int bodyIntValue = RadixConvertUtil.computeIntValue(bodyBytes[0x0]);
                if (bodyIntValue == 0xF0)
                    bodyDataItem.setValue("登录");
                else if (bodyIntValue == 0xF1)
                    bodyDataItem.setValue("退出登录");
                else if (bodyIntValue == 0xF2)
                    bodyDataItem.setValue("在线保持");

                result.add(bodyDataItem);
            } else if (functionCodeEnum == SL427_FrameFunctionCodeEnum.REPORT_DATA_C0H) {
                //自报实时数据，AFN=C0H
                //前面是每5个字节表示一个数据值，最后固定使用4字节表示报警状态+7字节表示时标
                //报警状态
                //时标，body的最后7字节
                byte[] timeBytes = Arrays.copyOfRange(bodyBytes, bodyBytes.length - 0x7, bodyBytes.length);
                LocalDateTime time = this.parseTime(timeBytes);
                if (time != null) {
                    IotFrameBodyDataItem bodyDataItem = new IotFrameBodyDataItem();
                    bodyDataItem.setBytes(bodyBytes);
                    bodyDataItem.setHexString(RadixConvertUtil.toHexString(bodyBytes));
                    bodyDataItem.setType(IotFrameBodyDataItemTypeEnum.TIME);
                    bodyDataItem.setCode("tm");
                    bodyDataItem.setName("时标");
                    bodyDataItem.setValue(time);

                    result.add(bodyDataItem);
                }

                logger.debug("正文内容长度为：" + bodyBytes.length + " 计算出为：" + functionCodeIntValue + " hex为：" + RadixConvertUtil.toHexString(functionCodeByte));
                //根据控制域中不同的命令类型确定含有的数据数量和每个数据使用的字节数
                if (commandTypeEnum == SL427_FrameControlCommandTypeEnum.RAIN) {
                    //报送雨量数据：数据域3+4字节，雨量值(3字节)+报警状态(4字节)，第1字节低4位表示十分位(0.1)，无符号位
                    int perDataValueBytesLength = 0x3;//每个/组数据的长度
                    int dataValueTotalLength = bodyBytes.length - 0x7 - 0x4;
                    int dataGroupCount = dataValueTotalLength / perDataValueBytesLength;
                    logger.debug("正文内容长度为：" + bodyBytes.length + " 数据域中数据总长度为：" + dataValueTotalLength + " 计算出的数据值数量为：" + dataGroupCount);
                    if (dataGroupCount > 0x0) {
                        //逐个解析数据值
                        for (int i = 0; i < dataGroupCount; i++) {
                            byte[] bytes = Arrays.copyOfRange(bodyBytes, i * perDataValueBytesLength, (i + 1) * perDataValueBytesLength);
                            int value = RadixConvertUtil.computeBcdIntValue(bytes, 0);
                            IotFrameBodyDataItem bodyDataItem = new IotFrameBodyDataItem();
                            bodyDataItem.setBytes(bodyBytes);
                            bodyDataItem.setHexString(RadixConvertUtil.toHexString(bodyBytes));
                            bodyDataItem.setType(IotFrameBodyDataItemTypeEnum.VALUE);
                            //判断是否有多组数据，如果有多组数据使用顺序编号
                            if (dataGroupCount == 0x1) {
                                bodyDataItem.setName(commandTypeEnum.getName());
                                bodyDataItem.setCode("rain");
                            } else {
                                bodyDataItem.setName(commandTypeEnum.getName() + (i + 1));
                                bodyDataItem.setCode("rain" + (i + 1));
                            }
                            //数据值系数转换, 0.1f
                            double dv = value * 0.1;
                            bodyDataItem.setValue(dv);

                            result.add(bodyDataItem);
                        }
                    }
                } else if (commandTypeEnum == SL427_FrameControlCommandTypeEnum.WATER) {
                    //报送水位数据：数据域4*N+4字节，每个水位值4字节，第1字节低4位表示百分位(0.01)，最后1字节高4位为符号位
                    int perDataValueBytesLength = 0x4;//每个/组数据的长度
                    int dataValueTotalLength = bodyBytes.length - 0x7 - 0x4;
                    int dataGroupCount = dataValueTotalLength / perDataValueBytesLength;
                    logger.debug("正文内容长度为：" + bodyBytes.length + " 数据域中数据总长度为：" + dataValueTotalLength + " 计算出的数据值数量为：" + dataGroupCount);
                    if (dataGroupCount > 0x0) {
                        //逐个解析数据值
                        for (int i = 0; i < dataGroupCount; i++) {
                            byte[] bytes = Arrays.copyOfRange(bodyBytes, i * perDataValueBytesLength, (i + 1) * perDataValueBytesLength);
                            int value = RadixConvertUtil.computeBcdIntValue(bytes, 4);
                            IotFrameBodyDataItem bodyDataItem = new IotFrameBodyDataItem();
                            bodyDataItem.setBytes(bodyBytes);
                            bodyDataItem.setHexString(RadixConvertUtil.toHexString(bodyBytes));
                            bodyDataItem.setType(IotFrameBodyDataItemTypeEnum.VALUE);
                            //判断是否有多组数据，如果有多组数据使用顺序编号
                            if (dataGroupCount == 0x1) {
                                bodyDataItem.setName(commandTypeEnum.getName());
                                bodyDataItem.setCode("water");
                            } else {
                                bodyDataItem.setName(commandTypeEnum.getName() + (i + 1));
                                bodyDataItem.setCode("water" + (i + 1));
                            }
                            //数据值系数转换, 0.01f
                            double dv = value * 0.01;
                            bodyDataItem.setValue(dv);

                            result.add(bodyDataItem);
                        }
                    }
                } else if (commandTypeEnum == SL427_FrameControlCommandTypeEnum.FLOW) {
                    //报送流量/水量数据：数据域10*N+4字节，每一组数据包含流量(5字节)+水量(5字节)，固定使用AAAAAAAAAA表示无数据
                    //计算数据值的字节数=body字节总数 - 7 (时标使用7个字节)，是否含有报警状态（如果含有使用4字节）
                    int perDataValueBytesLength = 0x5 + 0x5;//每个/组数据的长度
                    int dataValueTotalLength = bodyBytes.length - 0x7 - 0x4;
                    int dataGroupCount = dataValueTotalLength / perDataValueBytesLength;
                    logger.debug("正文内容长度为：" + bodyBytes.length + " 数据域中数据总长度为：" + dataValueTotalLength + " 计算出的数据值数量为：" + dataGroupCount);
                    //流量数据：第1字节低4位表示千分位(0.001)，最后1字节高2位为符号位，最后1字节高3-4位(D5-D4)表示单位（00B=m³/s,11B=m³/h）
                    //水量数据：第1字节低4位表示个位(1)，最后1字节高1位为符号位(固定为0，累计水量不能为负数)
                    if (dataGroupCount > 0x0) {
                        //逐个解析数据值
                        for (int i = 0; i < dataGroupCount; i++) {
                            //前5字节为流量，后5字节为累计水量
                            byte[] flowValueBytes = Arrays.copyOfRange(bodyBytes, i * perDataValueBytesLength, i * perDataValueBytesLength + 0x5);
                            byte[] sumValueBytes = Arrays.copyOfRange(bodyBytes, i * perDataValueBytesLength + 0x5, (i + 1) * perDataValueBytesLength);
                            //判断数值字节数组，如果无数据不进行转换
                            if (this.isNoData(flowValueBytes) == false) {
                                //流量数据：符号位使用2位，第1字节低4位为千分位
                                //最后1字节高3-4位(D5-D4)表示单位（00B=m³/s,11B=m³/h），在此去除最后1字节高3-4上的值
                                flowValueBytes[0x4] = (byte) (flowValueBytes[0x4] & 0xCF);//0xCF=1100 1111
                                int value = RadixConvertUtil.computeBcdIntValue(flowValueBytes, 2);
                                IotFrameBodyDataItem bodyDataItem = new IotFrameBodyDataItem();
                                bodyDataItem.setBytes(bodyBytes);
                                bodyDataItem.setHexString(RadixConvertUtil.toHexString(bodyBytes));
                                bodyDataItem.setType(IotFrameBodyDataItemTypeEnum.VALUE);
                                //判断是否有多组数据，如果有多组数据使用顺序编号
                                if (dataGroupCount == 0x1) {
                                    bodyDataItem.setName(commandTypeEnum.getName());
                                    bodyDataItem.setCode("flow");
                                } else {
                                    bodyDataItem.setName(commandTypeEnum.getName() + (i + 1));
                                    bodyDataItem.setCode("flow" + (i + 1));
                                }
                                //数据值系数转换, 0.001f
                                double dv = value * 0.001;
                                bodyDataItem.setValue(dv);

                                result.add(bodyDataItem);
                            }

                            //判断数值字节数组，如果无数据不进行转换
                            if (this.isNoData(sumValueBytes) == false) {
                                //累计水量数据：符号位使用1位，第1字节低4位为个位，保留到整数（个位）
                                int value = RadixConvertUtil.computeBcdIntValue(sumValueBytes, 1);//系数也可以传入1.0
                                IotFrameBodyDataItem bodyDataItem = new IotFrameBodyDataItem();
                                bodyDataItem.setBytes(bodyBytes);
                                bodyDataItem.setHexString(RadixConvertUtil.toHexString(bodyBytes));
                                bodyDataItem.setType(IotFrameBodyDataItemTypeEnum.VALUE);
                                //判断是否有多组数据，如果有多组数据使用顺序编号
                                if (dataGroupCount == 0x1) {
                                    bodyDataItem.setName(commandTypeEnum.getName());
                                    bodyDataItem.setCode("sumwater");
                                } else {
                                    bodyDataItem.setName(commandTypeEnum.getName() + (i + 1));
                                    bodyDataItem.setCode("sumwater" + (i + 1));
                                }
                                bodyDataItem.setValue(value);//不需要进行单位转换

                                result.add(bodyDataItem);
                            }
                        }
                    }
                } else {
                    logger.warn("帧命令类型为：" + commandTypeEnum + "的数值暂时未实现解析。");
                }
            }

            if (result.size() > 0x0)
                return result;
        }

        return null;
    }

    /**
     * 根据字节数组解析出时间，解析不出或者错误返回null
     *
     * @param bytes 字节数组
     * @return java.time.LocalDateTime
     * @author QuCheng Tech
     * @since 2025/11/13
     */
    LocalDateTime parseTime(byte[] bytes) {
        //传入字节数组错误或长度不够不进行解析
        if (bytes != null && bytes.length >= 0x7) {
            //7个字节表示，如：00001030032200 时间7字节，秒、分、时、日、月、年、延时
            //时标中均采用BCD码，每个数均使用高4位表示十位、低4位表示个位
            int year = RadixConvertUtil.computeBcdIntValue(bytes[0x5]);
            int month = RadixConvertUtil.computeBcdIntValue(bytes[0x4]);
            int day = RadixConvertUtil.computeBcdIntValue(bytes[0x3]);
            int hour = RadixConvertUtil.computeBcdIntValue(bytes[0x2]);
            int minute = RadixConvertUtil.computeBcdIntValue(bytes[0x1]);
            int second = RadixConvertUtil.computeBcdIntValue(bytes[0x0]);

            logger.debug("parseTime " + year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second);

            LocalDateTime time = LocalDateTime.of(2000 + year, month, day, hour, minute, second);

            logger.debug("parseTime hex:" + RadixConvertUtil.toHexString(bytes) + " 解析出的时间为：" + LocalDateTimeUtil.formatDateTime(time));

            return time;
        }

        return null;
    }

    /**
     * 判断指定的字节数组是否为无数据
     *
     * @param bytes 字节数组
     * @return boolean
     * @author QuCheng Tech
     * @since 2025/11/13
     */
    boolean isNoData(byte[] bytes) {
        if (bytes != null && bytes.length > 0x0) {
            for (int i = 0; i < bytes.length; i++) {
                if (bytes[i] != NO_VALUE_Byte)//任意一个字节不是无数据字节均判断为是有数据
                    return false;
            }
        }

        //如果传入的字节数组为空或者全部字节均判断为无数据
        return true;
    }

    /**
     * 生成回复报文，根据传入的报文解析结果判断，如果返回空表示不需要回复
     *
     * @param frameParseResult 收到的报文解析结果
     * @return byte[]
     * @author QuCheng Tech
     * @since 2025/11/8
     */
    @Override
    public byte[] generateReplyFrame(IotFrameParseResult frameParseResult) {
        logger.debug("generateReplyFrame");
        //如果解析结果为空或者解析错误不需要进行回复
        if (frameParseResult == null) {
            logger.debug("generateReplyFrame 传入的数据帧解析结果为null，无法生成回复报文");
        } else {
            if (frameParseResult.isParseSuccess() == false) {
                logger.debug("generateReplyFrame 传入的数据帧解析结果为解析失败，无法生成回复报文");
            } else {
                /* SL651 Hex 下行报文数据帧
                 * 结构框架：帧起始符 遥测站地址 中心站地址 密码 功能码 上下行标记及长度 报文起始符 报文正文 报文结束符 校验码
                 * 长度字节：   2       5        1      2    1        2          1       n        1     2
                 * 固定内容： 帧起始符-7E7E、报文起始符-02、报文结束符-03
                 * 解析举例：7E7E 0011223344 05 03E8 32 8008 02 0034170718110016 1B E5D4
                 * 解析举例：7E7E 0010100001 01 A000 32 8008 02 000C200724172349 06 DB17
                 * (遥测站定时报 32)报文正文结构：流水号 发报时间(YYMMddHHmmss)
                 * (遥测站定时报 32)报文正文长度：  2      6
                 * (遥测站定时报 32)报文正文解析：000C 200724172349
                 */

                //遥测站定时报 32
                //上行：7E7E05001122334403E832002B020034170718110016F1F1001122334448F0F01707181100201900004026190000403923000104903812109903A421
                //下行：7E7E00112233440503E83280080200341707181100161BE5D4--缺少报文结束符

                //根据当前协议进行判断是否需要回复
                if (frameParseResult.getFrameType() == IotPacketsDataFrameTypeEnum.DATA) {
                    //当前协议中只有数据包收到后需要回复
                    //SL651 HEX协议回复需要根据功能码来进行回复和确定回复的报文正文内容，如果没有功能码或者功能码错误不进行回复
                    if (frameParseResult.getFunctionCodeBytes() == null || frameParseResult.getFunctionCodeBytes().length < 0x1) {
                        logger.debug("generateReplyFrame 传入的数据帧解析结果中功能码为空，无法生成回复报文");
                    } else {
                        //功能码使用1个字节，获取功能码的值
                        byte functionCodeByte = frameParseResult.getFunctionCodeBytes()[0x0];
                        //功能码解析，根据功能码对数据值类型进行赋值，确定是心跳还是数据帧
                        SL427_FrameFunctionCodeEnum functionCodeEnum = SL427_FrameFunctionCodeEnum.getSL427_FrameFunctionCodeEnum(functionCodeByte);
                        if (functionCodeEnum.getIndex() == SL427_FrameFunctionCodeEnum.REPORT_DATA_C0H.getIndex()) {
                            //如果功能码是数据报送，生成对应回复报文

                        } else {
                            logger.debug("generateReplyFrame 传入的数据帧解析结果中功能码为[" + functionCodeEnum + "]，判定为无需生成回复报文");
                        }
                    }
                } else {
                    logger.debug("generateReplyFrame 传入的数据帧解析结果中数据帧类型为[" + frameParseResult.getFrameType() + "]，判定为无需生成回复报文");
                }
            }
        }

        return null;
    }
}
